19. Exercise: Coroutines for Long-running Operations
4 Exercise Coroutines For Long-Running Operations - I 2020
Important Update:
The video above shows creating the CoroutineScopeuiScope, and attaching the ViewModel Job. Creating own scope is no longer recommended by Google. The recommended way is to use a lifecycle-aware coroutine scope ViewModelScope provided by the Architecture components.
ViewModels provide their own scope by default, which can be accessed by ViewModelScope. As a result, we do not need another coroutine in IO contextwithContext(Dispatchers.IO) {}inside suspending function definitions.
Read the reference material for more information, and follow the updated instructions below.
L6 38 Display The Data SC
Now it's your turn to complete this exercise yourself.
In this step, you'll use coroutines to implement the app's database calls and display the sleep data.
Open the
SleepTrackerViewModel.ktfile.Define a variable,
tonight, to hold the current night, and make itMutableLiveData:
private var tonight = MutableLiveData<SleepNight?>()
- Define a variable,
nights. ThengetAllNights()from the database and assign to thenightsvariable:
private val nights = database.getAllNights()
- To initialize the
tonightvariable, create aninitblock and callinitializeTonight(), which you'll define in the next step:
init {
initializeTonight()
}
Implement
initializeTonight(). Use theviewModelScope.launch{}to start a coroutine in the ViewModelScope.Inside, get the value for
tonightfrom the database by callinggetTonightFromDatabase(), which you will define in the next step, and assign it totonight.value:
private fun initializeTonight() {
viewModelScope.launch {
tonight.value = getTonightFromDatabase()
}
}
Implement
getTonightFromDatabase(). Define is as a private suspend function that returns a nullableSleepNight, if there is no current startedsleepNight.This leaves you with an error, because you will have to return something.
private suspend fun getTonightFromDatabase(): SleepNight? { }
- Let the coroutine get tonight from the database. If the start and end times are the not the same, meaning, the night has already been completed, return null. Otherwise, return
night:
var night = database.getTonight()
if (night?.endTimeMilli != night?.startTimeMilli) {
night = null
}
return night
- Implement
onStartTracking(), the click handler for the Start button:
fun onStartTracking() {}
- Inside
onStartTracking(), launch a coroutine inviewModelScope:
viewModelScope.launch {}
- Inside the coroutine, create a new
SleepNight, which captures the current time as the start time:
val newNight = SleepNight()
- Call
insert()to insert it into the database. You will defineinsert()shortly:
insert(newNight)
- Set tonight to the new night:
tonight.value = getTonightFromDatabase()
- Define
insert()as a private suspend function that takes aSleepNightas its argument:
private suspend fun insert(night: SleepNight)
- For the body of
insert(), insert thenightinto the database:
database.insert(night)
Add Click Handlers for the Buttons
Add
onStopTracking()to the view model. Launch a coroutine in theviewModelScope.If it hasn't been set yet, set the
endTimeMillito the current system time and callupdate()with the night. There are several ways to implement this, and one is shown below:
fun onStopTracking() {
viewModelScope.launch {
val oldNight = tonight.value ?: return@launch
oldNight.endTimeMilli = System.currentTimeMillis()
update(oldNight)
}
}
- Implement
update()using the same pattern asinsert():
private suspend fun update(night: SleepNight) {
database.update(night)
}
- Analogously, implement
onClear()andclear():
fun onClear() {
viewModelScope.launch {
clear()
tonight.value = null
}
}
suspend fun clear() {
database.clear()
}
- Open
fragment_sleep_tracker.xmland add click handlers to the three buttons:
android:onClick="@{() -> sleepTrackerViewModel.onStartTracking()}"
android:onClick="@{() -> sleepTrackerViewModel.onStopTracking()}"
android:onClick="@{() -> sleepTrackerViewModel.onClear()}"
Display the data
- Add code to transform
nightsinto anightsStringusing theformatNights()function fromUtil.kt:
val nightsString = Transformations.map(nights) { nights ->
formatNights(nights, application.resources)
}
- In
fragment_sleep_tracker.xml, in theTextView, in theandroid:textproperty, replace the resource string with a reference tonightsString:
"@{sleepTrackerViewModel.nightsString}"
In
Util.ktand uncomment the commented code.** Rebuild and run your code.**
If you want to start at this step, you can download this exercise from: Step.05-Exercise-Coroutines.
You will find plenty of //TODO comments to help you complete this exercise, and if you get stuck, go back and watch the video again.
Once you’re done, you can check your solution against the solution we’ve provided here: Step.05-Solution-Coroutines, or using this git diff.
Task Feedback:
Well done! This was a big task!
Android Developer Documentation
Other documentation and articles
CDATA stands for Character Data and it means that the data in between these strings includes data that could be interpreted as XML markup, but should not be.